package cc.fine.svc.main.service.impl.tbl;

import cc.fine.svc.config.redis.utils.RedisCache;
import cc.fine.svc.main.bean.VideoOpCount;
import cc.fine.svc.main.bean.dto.UserInfoDto;
import cc.fine.svc.main.bean.dto.VideoInfoDto;
import cc.fine.svc.main.bean.es.UserSearchEsEntity;
import cc.fine.svc.main.bean.es.VideoEsEntity;
import cc.fine.svc.main.bean.po.*;
import cc.fine.svc.main.bean.vo.VideoUploadVo;
import cc.fine.svc.main.bean.vo.VideoViewVo;
import cc.fine.svc.main.mapper.TblVideoMapper;
import cc.fine.svc.main.service.VideoCountService;
import cc.fine.svc.main.service.rel.RelUserVideoCollectService;
import cc.fine.svc.main.service.rel.RelUserVideoFavorService;
import cc.fine.svc.main.service.rel.RelUserVideoPublishService;
import cc.fine.svc.main.service.rel.RelVideoTagService;
import cc.fine.svc.main.service.tbl.TblUserCountService;
import cc.fine.svc.main.service.tbl.TblUserService;
import cc.fine.svc.main.service.tbl.TblVideoService;
import cc.fine.svc.main.utils.EsUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author wyihe
 * @since 2023-10-25
 */
@Service
@Transactional
public class TblVideoServiceImpl extends ServiceImpl<TblVideoMapper, TblVideo> implements TblVideoService {

    @Autowired
    private RelUserVideoPublishService publishService;

    @Autowired
    private RelVideoTagService relVideoTagService;

    @Autowired
    private VideoCountService videoCountService;

    @Autowired
    private TblUserCountService userCountService;

    @Autowired
    private TblUserService userService;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private RelUserVideoCollectService collectService;

    @Autowired
    private RelUserVideoFavorService favorService;

    @Autowired
    private EsUtil esUtil;

    @Override
    public void upload(String userId, VideoUploadVo videoVo) {

        // 存 video
        TblVideo video = new TblVideo();
        BeanUtils.copyProperties(videoVo, video);
        String videoId = UUID.randomUUID().toString();
        video.setId(videoId);
        save(video);

        // 存 publish 关联表
        RelUserVideoPublish publish = new RelUserVideoPublish();
        publish.setUserId(userId);
        publish.setVideoId(videoId);
        publishService.save(publish);

        // publish count + 1
        TblUserCount userCount = userCountService.getById(userId);
        userCount.setPublishs(userCount.getPublishs() + 1);
        userCountService.saveOrUpdate(userCount);

        // 存标签id
        List<String> tagIds = videoVo.getTagIds();
        List<RelVideoTag> relVideoTagList = new ArrayList<>();
        tagIds.forEach(id -> {
            if (!StringUtils.isEmpty(id))
                relVideoTagList.add(new RelVideoTag(null, id, videoId));
        });
        relVideoTagService.saveBatch(relVideoTagList);

        // 视频信息存到 es 中
        TblUser user = userService.getById(userId);
        VideoEsEntity entity = new VideoEsEntity(video, user, new VideoOpCount(), "", videoVo.getLocalMom());
        try {
            esUtil.addData(JSON.toJSONString(entity), Es_Index, videoId);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // es 充 publishes + 1
        UserSearchEsEntity userSearchEsEntity = new UserSearchEsEntity();
        userSearchEsEntity.setId(userId);
        userSearchEsEntity.setPublishs(userCount.getPublishs());
        try {
            esUtil.updateDataById(userSearchEsEntity, TblUserService.User_Search_Es_Index, userId);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    @Override
    public VideoInfoDto getInfo(String videoId) {
        TblVideo video = getById(videoId);
        if (video == null) video = getById("33eb95bb-cea6-47b6-a7c8-65b77281f88b");
        VideoOpCount count = videoCountService.get(videoId);

        VideoInfoDto res = new VideoInfoDto();
        BeanUtils.copyProperties(video, res);
        BeanUtils.copyProperties(count, res);

        List<TblTag> tages = relVideoTagService.tages(videoId);
        res.setTags(tages);

        return res;
    }

    @Override
    public VideoViewVo getVideoViewVo(String videoId, String userId) {
        // 自定义缓存，主要是为了设置过期时间
        VideoViewVo res = redisCache.getCacheObject(VIEW_CACHE + "::" + videoId);
        if (res != null) {
            return res;
        }

        // 获取视频信息
        VideoInfoDto videoInfo = getInfo(videoId);

        // 获取视频下的用户信息
        QueryWrapper<RelUserVideoPublish> w = new QueryWrapper<>();
        w.eq("video_id", videoId);
        String publishId = publishService.getOne(w).getUserId();
        UserInfoDto userInfo = userService.getInfo(publishId);

        // 合并信息
        res = new VideoViewVo();
        BeanUtils.copyProperties(videoInfo, res);
        res.setUser(userInfo);

        // 查询是否点赞、是否收藏
        boolean isFavor = favorService.isFavor(userId, videoId);
        List<TblCollect> collects = collectService.searchCollect(userId, videoId);
        res.setIsFavor(isFavor);
        res.setIsCollect(!collects.isEmpty());
        res.setCollects(collects);


        // 缓存收尾，存入redis增加过期时间
        redisCache.setCacheObject(VIEW_CACHE + "::" + videoId, res, VIEW_CACHE_TIME, TimeUnit.SECONDS);

        return res;
    }

    @Override
    public List<VideoViewVo> getVideoViewVoBatch(List<String> videoIds, String userId) {

        List<VideoViewVo> res = new ArrayList<>();

        videoIds.forEach(videoId -> {
            VideoViewVo vo = getVideoViewVo(videoId, userId);
            res.add(vo);
        });

        return res;
    }

    @Override
    public List<Map<String, Object>> search(String keyword, Integer page, Integer limit) {
        List<Map<String, Object>> res;

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder
                .query(
                        QueryBuilders
                                .multiMatchQuery(keyword,
                                        "video.title", "video.detail", "tagStr")

                )
                .sort("count.favorCount", SortOrder.DESC);

        try {
            res = esUtil.searchListData("video", builder, limit, page * limit, "", "", "");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    @Override
    public List<Map<String, Object>> searchForIp(String ip, Integer page, Integer limit) {
        List<Map<String, Object>> res;

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder
                .query(
                        QueryBuilders
                                .multiMatchQuery(ip,
                                        "localMom")
                )
                .sort("count.favorCount", SortOrder.DESC);

        try {
            res = esUtil.searchListData("video", builder, limit, page * limit, "", "", "");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    @Override
    public List<VideoInfoDto> getInfoBatch(List<String> videoIds) {
        List<VideoInfoDto> res = new ArrayList<>();

        videoIds.forEach(id -> {
            VideoInfoDto dto = getInfo(id);
            res.add(dto);
        });

        return res;
    }

    @Override
    public List<VideoInfoDto> publishVideoInfoList(String userId) {
        QueryWrapper<RelUserVideoPublish> w = new QueryWrapper<>();
        w.eq("user_id", userId);
        List<String> videoIds = publishService.list(w).stream()
                .map(x -> x.getVideoId())
                .collect(Collectors.toList());

        return getInfoBatch(videoIds);

    }

    @Override
    public List<Map<String, Object>> getInfoByTagId(String tagId, Integer page, Integer limit) {
        // 找到标签下的视频id
        List<String> videoIds = relVideoTagService.videoIds(tagId);

        return searchByVideoIds(videoIds, page, limit);
    }

    @Override
    public List<Map<String, Object>> searchByVideoIds(List<String> videoIds) {

        return searchByVideoIds(videoIds, 0, 20000000);
    }

    @Override
    public List<Map<String, Object>> searchByVideoIds(List<String> videoIds, Integer page, Integer limit) {
        List<Map<String, Object>> res;

        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder
                .query(
                        QueryBuilders
                                .termsQuery("video.id.keyword", videoIds.toArray())
                )
                .sort("count.favorCount", SortOrder.DESC);

        try {
            res = esUtil.searchListData("video", builder, limit, page * limit, "", "", "");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return res;
    }

    @Override
    public List<VideoViewVo> fanVideos(String userId, Integer page, Integer limit) {

        List<String> videoIds = getFanVideoIds(userId, page, limit);
        return getVideoViewVoBatch(videoIds, userId);

    }

    @Override
    public void removeVideo(String videoId) {
        // tbl_video 表删除
        removeById(videoId);

        // rel_user_video_publish
        QueryWrapper<RelUserVideoPublish> w = new QueryWrapper<>();
        w.eq("video_id", videoId);
        publishService.remove(w);

        // es video 索引删除
        try {
            esUtil.deleteDataById(TblVideoService.Es_Index, videoId);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private List<String> getFanVideoIds(String userId, Integer page, Integer limit) {
        List<String> list = baseMapper.selectGetFanVideoIds(userId, page * limit, limit);
        if (list == null) list = new ArrayList<>();
        return list;
    }
}
